package windowlimiter

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"

	"gitee.com/mqyqingkong/limiter"
)

type rollWindowLimiter struct {
	aquireNums   []int64
	currentIndex int
	windowSize   int
	//max acquire times
	limit             int64
	lastIndex         int
	lastAbsuluteIndex int64
	startTime         time.Time
	lock              sync.Mutex
}

// NewRollWindowLimiter 基于滚动窗口（window）限流。window时间窗口内，最多获取limit个许可。
func NewRollWindowLimiter(window time.Duration, limit int64) limiter.Limiter {
	windowSize := int(window.Seconds())
	windowLimiter := &rollWindowLimiter{
		aquireNums:   make([]int64, windowSize),
		currentIndex: 0,
		windowSize:   windowSize,
		limit:        limit,
		lastIndex:    -1,
		startTime:    time.Now(),
	}
	return windowLimiter
}

func (w *rollWindowLimiter) debugInfo() string {
	sb := strings.Builder{}
	sb.WriteString("aquireNums:\n")
	sb.WriteString(fmt.Sprintln(w.aquireNums))
	sb.WriteString("lastIndex:")
	sb.WriteString(fmt.Sprintln(w.lastIndex))
	sb.WriteString("currentIndex:")
	sb.WriteString(fmt.Sprintln(w.currentIndex))
	return sb.String()
}

// Acquire 请求许可，返回值为true时，表示已获得许可，为false时，表示未获得许可。
func (w *rollWindowLimiter) Acquire() bool {
	w.lock.Lock()
	defer w.lock.Unlock()

	untilSecs := int64(time.Since(w.startTime) / time.Second)
	var ws = int64(w.windowSize)
	w.currentIndex = int(untilSecs % ws)
	//log.Printf("limiter currentIndex: %d, lastIndex: %d", w.currentIndex, w.lastIndex)
	forceClear := false
	if untilSecs-w.lastAbsuluteIndex >= ws {
		forceClear = true
	}
	w.resetLimit(forceClear)
	if w.acquireSum() >= w.limit {
		return false
	} else {
		w.aquireNums[w.currentIndex%w.windowSize]++
	}
	w.lastIndex = w.currentIndex
	w.lastAbsuluteIndex = untilSecs
	return true
}

func (w *rollWindowLimiter) String() string {
	return fmt.Sprintf("rolling window duration:%d s, acquire limit:%d", w.windowSize, w.limit)
}

func (w *rollWindowLimiter) resetLimit(forceClear bool) {
	if forceClear {
		for i := 0; i < w.windowSize; i++ {
			w.aquireNums[i] = 0
		}
	} else if w.lastIndex <= w.currentIndex {
		for i := w.lastIndex + 1; i <= w.currentIndex; i++ {
			w.aquireNums[i] = 0
		}
	} else {
		for i := w.lastIndex + 1; i < w.windowSize; i++ {
			w.aquireNums[i] = 0
		}
		for i := 0; i <= w.currentIndex; i++ {
			w.aquireNums[i] = 0
		}
	}

}

func (w *rollWindowLimiter) acquireSum() int64 {
	var sum int64 = 0
	for _, v := range w.aquireNums {
		sum += v
	}
	return sum
}

// TryAcquire 尝试获取许可，返回值为nil时，表示获得许可，否则未获得许可。
func (w *rollWindowLimiter) TryAcquire(ctx context.Context) error {
	acquired := w.Acquire()
	if acquired {
		return nil
	}

	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
	}

	// Determine wait limit
	now := time.Now()
	waitLimit := InfDuration
	if deadline, ok := ctx.Deadline(); ok {
		waitLimit = deadline.Sub(now)

	}
	t := time.NewTimer(waitLimit)
	defer t.Stop()
	for {
		acquired := w.Acquire()
		if acquired {
			return nil
		}
		select {
		case <-t.C:
			return nil
		case <-ctx.Done():
			return ctx.Err()
		default:
			time.Sleep(time.Second)
		}
	}
}
